Databinding 双向绑定详解
Android-Architecture-Components官方文档
Databinding是Google推出的一个支持View与ViewModel绑定的Library,可以说Databinding建立了一个UI与数据模型之间的桥梁,即UI的变化可以通知到ViewModel, ViewModel的变化同样能够通知到UI从而使UI发生改变,大大减少了之前View与Model之间的胶水代码,如findViewById, 改变及获取TextView的内容还需要调用setText()、 getText(),获取EditText编辑之后的内容需要调用getText(),而有了Databinding的双向绑定,这些重复的工作都将被省去。下面我们就来看一下如何使用Databinding来双向绑定
首先我们先定一个ViewModel,将这个ViewModel的变量content与布局文件中的TextView绑定:
1 | class MyViewModel: ViewModel(){ |
官方支持的双向绑定
这里的官方支持指的是Databinding库中已经定义了一些View的双向绑定,我们如果要使用的话只需要将xml文件中的”@{}”改成”@={}”, 如下代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23(test_layout.xml):
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.fb.onedayimprove.viewmodel.MyModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{model.content}"
/>
<!--android:text="@={model.content}"-->
</LinearLayout>
</layout>上述代码中注释部分就是将单向绑定改为双向绑定的代码
官方支持的双向绑定:
- AbsListView android:selectedItemPosition
- CalendarView android:date
- CompoundButton android:checked
- DatePicker android:year, android:month, android:day
- NumberPicker android:value
- RadioGroup android:checkedButton
- RatingBar android:rating
- SeekBar android:progress
- TabHost android:currentTab
- TextView android:text
- TimePicker android:hour, android:minute
那么双向绑定是怎么实现的呢?首先来看一下Databinding的源码:
首先是我们在编译之后会生成几个相关文件如test_layout.xml, test_layout-layout.xml, TestLayoutBinding.java, BR文件等。我们主要来看一下TestLayoutBinding.java这个文件。这个文件的主要作用是声明xml内的View控件以及声明的ViewModel,如本例中声明了一个TextView:
1 |
|
上述代码中mboundView0为根布局、mboundView1为声明了双向绑定的TextView,mModel为与View绑定的ViewModel。(注意:并不是所有的变量都会被声明,有三种情况:在xml布局中声明id的,将会被定义为静态变量,可以直接通过Databinding对象访问;引用了model数据的;根布局)
接下来当我们定义了双向绑定的时候,TestLayoutBinding.java会生成这样一段代码:
1 | private android.databinding.InverseBindingListener mboundView1androidTextAttrChanged = new android.databinding.InverseBindingListener() { |
通过上述代码可以看到实现逆向绑定的关键部分,可以看到调用InverseBindingListener接口的onChange()方法就可以将view的值传回ViewModel。那么它在哪里被用到呢?看下面的代码:
1 |
|
可以看到在executeBindings这个方法中,将InverseBindingListener 的值传给setTextWatcher,这个方法怎么来的后面会提到。
上面的代码中提到一个TextViewBindingAdapter,通过它的名字就可以看出是绑定View与ViewModel的适配器,那么来看一下TextViewBindingAdapter做了什么 (在包android.databinding.adapters下):
1 | "android:text") ( |
上述代码相信大家并不陌生,当Databinding在给某一个控件的XXX属性赋值的时候,需要去找到相应的setXXX()方法,然后将model中的值给这个View。而@BindingAdapter (“xxx”)正是将这个setXXX()方法与xxx属性关联起来,所以上面部分的代码作用总结起来就是做了view.setText(text)。(注:可以看到代码中有对新旧内容的比较,只有当内容不同的时候才会执行下一步,这是为了防止双向绑定循环调用)
再来看下一个方法:
1 | "android:text", event = "android:textAttrChanged") (attribute = |
这个方法在TestLayoutBinding.java中已经说明了,是双向绑定取值时调用的方法,@InverseBindingAdapter注解和@BindingAdapter注解作用类似。
再来看下一个关键方法:
1 | "android:beforeTextChanged", "android:onTextChanged", (value = { |
从上面的代码我们可以看到@BindingAdapter绑定了几个属性,其中有一个叫做”android:textAttrChanged”,那么这个属性就代表了当TextView的text属性变化,xxxAttrChanged。可以想象得到那这个方法就是在android:text属性发生变化的时候会被调用。我们在这个方法中设置了对android:text这个属性内容变化的监听(注意是内容变化,不是属性变化),以后当每次改内容变化的时候,就会调用textAttrChanged.onChange()这个方法将TextView的值传回给ViewModel。
上述的是通过官方支持的源码来看双向绑定,那么我们要自定义的View使用双向绑定应该怎么做呢?
自定义View双向绑定
通过对官方源码分析,我们发现要实现双向绑定关键是需要实现Adapter,实现setter,getter,以及Listene方法。下面就让我们来实现一个自定义View 的双向绑定吧。
对MyModel稍作修改:
1 | class MyModel: ViewModel(){ |
对test_layout.xml修改(主要是使用自定义的View),加了一个点击事件,点击之后修改View的内容:
1 | <com.fb.onedayimprove.widget.TestEditView |
这里我们引入一个自定义View:这是一个类似表单的View,左边是个小标题,右边可以填一些信息等:
1 | class TestEditView: LinearLayout{ |
省略了部分代码,主要就是定义了一个内容改变的Listener。
下面是TestEditViewAdapter的代码:
1 | //使用InverbaseBindingMethod注解,与使用@BindingAdapter效果一样其中,event、method可声明也可以不声明,不声明的话会默认设置xxxAttrChanged 与 getXXX() 方法 |
接下来我们只需要在Fragment中为content赋值,然后运行程序,点击TestEditView,就可以看到Log输出了content的初始值与改变后的值(不贴图展示了)。
1 | val binding = TestLayoutBinding.inflate(inflater!!, container, false) |
总结
以上就是Databinding双向绑定的使用方法,总的来说应该注意两点:1、修改"@{}" 为 "@={}" 2、写setXXX(), xxxAttrChanged(), getXXX()方法。